﻿using System;

using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Drawing;
using System.Diagnostics;
using System.ComponentModel;
using Silvermoon.Windows;

namespace Silvermoon.Controls
{
    /// <summary>
    /// Displays data values virtualized in <see cref="ListViewItem"/>.
    /// </summary>
    public class ListView : ScrollViewer, IItemHost
    {
        // remark: InvalidationFlag.Internal is used to recalculate the offsets of items.

        #region fields

        private ItemEventArgs itemArgs = new ItemEventArgs();
        private int selectedItemIndex = -1;
        private IItemHost selectedItemHost;
        private int startIndexToRecalculate = -1;
        private ItemsContainer itemContainer;
        private IList itemsSource;
        private int virtualHeight;
        private bool alwaysShowGroupHeader;
        private int defaultItemHeight = 80;
        private List<ItemTopHeight> itemTopHeights;
        private Dictionary<int, Item> virtualItems = new Dictionary<int, Item>();
        private ItemContainerCollection items;
        private ListViewItemAnimator animator = new ListViewItemAnimator();

        #endregion
        #region ctor/dtor

        public ListView()
            : base()
        {
            itemContainer = new ItemsContainer();
            VisibleControls.AddLast(itemContainer);
        }

        protected override void Dispose(bool dispose)
        {
            DisposeVisibleItems();
            ItemsSource = null;
            DisposeItems();
            base.Dispose(dispose);
        }

        #endregion
        #region props

        /// <summary>
        /// Gets or sets whether to release items if they come out of view, or if they are kept alive.
        /// </summary>
        public bool KeepItems { get; set; }

        /// <summary>
        /// Gets all the items which are currently attached to the listview.
        /// </summary>
        public IEnumerable<Item> AttachedItems
        {
            get { return virtualItems.Values; }
        }

        /// <summary>
        /// Gets or sets whether the listview supports selection of more than one item.
        /// Note that in this case the IsSelected state of each item must be reflected in the data.
        /// </summary>
        public bool Multiselect { get; set; }

        public int SelectedItemIndex
        {
            get { return selectedItemIndex; }
            set
            {
                if (selectedItemIndex != value)
                {
                    int oldValue = selectedItemIndex;
                    selectedItemIndex = value;
                    OnSelectionChanged(oldValue);
                }
            }
        }

        public bool Squeeze { get; set; }

        public override Rectangle ClientBounds
        {
            get
            {
                Rectangle r = base.ClientBounds;
                return ContainerMargin.Layout(r, r.Size);
            }
        }

        private Margin containerMargin;

        /// <summary>
        /// Gets or sets whether the group header for the topmost group is always visible.
        /// </summary>
        public bool AlwaysShowGroupHeader
        {
            get { return alwaysShowGroupHeader; }
            set
            {
                if (alwaysShowGroupHeader != value)
                {
                    alwaysShowGroupHeader = value;
                    OnAlwaysShowGroupHeaderChanged();
                }
            }
        }

        /// <summary>
        /// Gets the list of none virtualized items.
        /// Note that you can either use Items or ItemsSource, but not both.
        /// </summary>
        public BindingList<Item> Items
        {
            get
            {
                if (items == null)
                {
                    items = new ItemContainerCollection(this);
                    ItemsSource = items;
                }
                return items;
            }
        }

        /// <summary>
        /// Gets or sets the height for a list item;
        /// </summary>
        public int ItemHeight
        {
            get { return defaultItemHeight; }
            set
            {
                if (defaultItemHeight != value)
                {
                    int oldValue = defaultItemHeight;
                    defaultItemHeight = value;
                    OnDefaultItemHeightChanged();
                }
            }
        }

        /// <summary>
        /// Gets the number of items in the view.
        /// </summary>
        public int Count
        {
            get { return itemsSource != null ? itemsSource.Count : 0; }
        }

        /// <summary>
        /// Gets or sets the items source that contains data to display.
        /// Note that you can either use Items or ItemsSource, but not both.
        /// </summary>
        public IList ItemsSource
        {
            get { return itemsSource; }
            set
            {
                if (itemsSource != value)
                {
                    IList oldValue = value;
                    if (value != items)
                    {
                        DisposeItems();
                        items = null;
                    }
                    itemsSource = value;
                    OnItemsSourceChanged(oldValue);
                }
            }
        }

        /// <summary>
        /// Gets the margins for the container that hosts the items.
        /// </summary>
        public Margin ContainerMargin
        {
            get
            {
                return containerMargin ?? Margin.Empty;
            }
            set
            {
                if (containerMargin != value)
                {
                    containerMargin = value;
                    Invalidate(InvalidationFlag.LayoutAppearance);
                }
            }
        }


        /// <summary>
        /// Gets the height of the content. If this value is larger than the control's height, a scrollbar becomes available.
        /// </summary>
        /// <value></value>
        public override int VirtualHeight
        {
            get { return virtualHeight; }
        }


        #endregion
        #region methods


        // TODO: Implement
        /// <summary>
        /// Sets an item to be in drag state and the position relative to the ListView.
        /// </summary>
        /// <param name="item">index of the item  to be in drag state, otherwise -1.</param>
        /// <param name="y">top position relative to the listView for the specified item.</param>
        public void DragItem(int index, int y)
        {
            throw new NotImplementedException();
        }

        protected virtual void OnSelectionChanged(int oldValue)
        {
            if (!Multiselect)
            {
                if (virtualItems.ContainsKey(oldValue))
                {
                    virtualItems[oldValue].IsSelected = false;
                }
            }
            if (virtualItems.ContainsKey(selectedItemIndex))
            {
                virtualItems[selectedItemIndex].IsSelected = true;
            }
        }

        private void SetVirtualHeight(int value)
        {
            if (virtualHeight != value)
            {
                virtualHeight = value;
                OnVirtualHeightChanged();
            }
        }

        protected virtual void OnVirtualHeightChanged()
        {
            if (IsNothingVisible()) EnsureRestState(true);
            if (VirtualHeightChanged != null) VirtualHeightChanged(this, EventArgs.Empty);
        }

        private bool IsNothingVisible()
        {
            int vh = VirtualHeight;
            if (vh <= Height && YOffset != 0) return true;
            return YOffset >= vh;
        }

        public void InvalidateItemsSource()
        {
            InvalidateList();
        }

        public void BindItems()
        {
            // todo: can be moved to ListViewController.

        }


        private void DisposeItems()
        {
            DisposeObject(items);
            items = null;
        }

        protected virtual void OnDefaultItemHeightChanged()
        {

        }

        protected virtual void DisposeVisibleItems()
        {
            foreach (Item item in virtualItems.Values)
            {
                OnReleaseVirtualItem(item);
            }
            virtualItems.Clear();
            itemContainer.VisibleControls.Clear();
        }

        internal protected virtual void OnReleaseVirtualItem(Item item)
        {
            item.RemoveAnimationStates();
            if (item != item.DataValue)
            {
                if (ReleaseVirtualItem != null)
                {
                    ItemEventArgs e = CreateItemEventArgs(item.ItemIndex, item, item.DataValue, item.Host);
                    ReleaseVirtualItem(this, e);
                    item.Release();
                }
                else
                {
                    item.Release();
                    item.Dispose();
                }
            }
        }

        private ItemEventArgs CreateItemEventArgs(int index, Item item, object value, IItemHost host)
        {
            itemArgs.Index = index;
            itemArgs.Item = item;
            itemArgs.Value = value;
            itemArgs.Host = host;
            return itemArgs;
        }

        protected Item GetCachedVirtualItem(int index, IItemHost host)
        {
            return host.GetRealizedItem(index);
        }


        public Item GetRealizedItem(int index)
        {
            Item result;
            return virtualItems.TryGetValue(index, out result) ? result : null;
        }


        internal protected virtual Item OnGetVirtualItem(int index, object data, ListViewGroup group)
        {
            Item vItem = data as Item;
            if (vItem != null)
            {
                return vItem;
            }
            if (group == null && virtualItems.TryGetValue(index, out vItem)) return vItem;

            if (GetVirtualItem != null)
            {
                IItemHost host = group;
                if (host == null) host = this;
                ItemEventArgs e = CreateItemEventArgs(index, null, data, host);
                GetVirtualItem(this, e);
                return e.Item;
            }
            else
            {
                return null;
            }
        }

        protected virtual void OnItemsSourceChanged(IList oldValue)
        {
            IBindingList bindingList = oldValue as IBindingList;
            if (bindingList != null) bindingList.ListChanged -= OnBindingListChanged;

            bindingList = itemsSource as IBindingList;
            if (bindingList != null)
            {
                bindingList.ListChanged += new ListChangedEventHandler(OnBindingListChanged);
            }

            InvalidateList();

        }

        private void OnBindingListChanged(object sender, ListChangedEventArgs e)
        {
            switch (e.ListChangedType)
            {
                case ListChangedType.ItemAdded:
                    this.OnItemInserted(e.NewIndex);
                    break;

                case ListChangedType.ItemDeleted:
                    this.OnItemRemoved(e.OldIndex);
                    break;

                case ListChangedType.ItemMoved:
                case ListChangedType.Reset:
                    InvalidateList();
                    break;
            }
        }

        private void InvalidateList()
        {
            DisposeVisibleItems();
            if (itemTopHeights != null) itemTopHeights.Clear();

            if (itemsSource == null)
            {
                SetVirtualHeight(0);
            }
            else
            {
                itemTopHeights = new List<ItemTopHeight>(itemsSource.Count);
                int top = 0;
                for (int index = 0; index < itemsSource.Count; index++)
                {
                    object data = itemsSource[index];
                    int h = GetItemHeight(index, data);
                    ItemTopHeight topHeight = new ItemTopHeight(top, h);
                    itemTopHeights.Add(topHeight);
                    top += h;
                }
                SetVirtualHeight(top);
            }
            Invalidate(InvalidationFlag.Layout);
        }

        protected virtual int GetItemHeight(int index, object data)
        {
            Item item = data as Item;
            if (item != null)
            {
                return item.Height + item.Margin.Height;
            }
            else
            {
                return defaultItemHeight;
            }
        }

        protected override void OnLayout()
        {
            base.OnLayout();

            bool layoutItems = CheckFlag(InvalidationFlag.Size | InvalidationFlag.Layout);
            if (CheckFlag(InvalidationFlag.UpdateChildren))
            {
                UpdateChildren();
            }
            do
            {
                if (CheckFlag(InvalidationFlag.Internal))
                {
                    RecalculateItemTopOffsets(startIndexToRecalculate);
                    InvalidationMask &= ~InvalidationFlag.Internal;
                }
                if (layoutItems)
                {
                    itemContainer.Bounds = ClientBounds;
                    LayoutItems();
                    if (Squeeze) SqueezeContainer();
                }
                //         if ((InvalidationMask & InvalidationFlag.Internal) == 0)
                {
                    // note:
                    // when an item calls SetItemHeight at LayoutItems(), items must not preloaded, otherwise they loose
                    // their invalidation mask and would not be layouted correclty, affter the virtual height of the listview is recalculated.
                    if (CheckFlag(InvalidationFlag.Size))
                    {
                        PreLoadItems();
                    }
                }


                // note that it's possible that the height of one or more items change during LayoutItems.
                // therefore, there is this loop, which reruns RecalculateItemTopOffset.
            } while (CheckFlag(InvalidationFlag.Internal));
        }

        private void PreLoadItems()
        {
            Control c = VisibleControls.First;
            while (c != null)
            {
                c.PreLoad();
                c = c.NextControl;
            }
        }

        private StretchTransform containerScale = new StretchTransform();

        private void SqueezeContainer()
        {
            int yOffset = this.yOffset;
            if (yOffset < 0)
            {
                int h = Math.Min(VirtualHeight, 4 * Height / 3);
                int scaleHeight = h + (3 * yOffset / 4);

                itemContainer.Transformation = containerScale;
                containerScale.YOffset = (float)h;
                containerScale.Y = (float)scaleHeight / (float)h;
            }
            else if (yOffset > Math.Max(0, VirtualHeight - Height))
            {
                int dh = Math.Max(0, VirtualHeight - Height);
                int dy = 3 * (yOffset - dh) / 4;
                int h = Math.Min(dh, 4 * Height / 3);
                if (h == 0) h = 1;
                int scaleHeight = h - dy;

                itemContainer.Transformation = containerScale;
                containerScale.YOffset = -(float)(h - Height);
                containerScale.Y = (float)scaleHeight / (float)h;

            }
            else
            {
                itemContainer.Transformation = null;
            }
        }

        private void UpdateChildren()
        {

            foreach (Item item in virtualItems.Values)
            {
                item.Update();
            }
        }

        /// <summary>
        /// Sets a new height for an item.
        /// </summary>
        /// <param name="index">Index of the item.</param>
        /// <param name="height">new height.</param>
        public virtual void SetItemHeight(int index, int height)
        {
            if (IsDisposing) return;
            if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException("index");
            if (index < itemTopHeights.Count)
            {
                ItemTopHeight th = itemTopHeights[index];
                if (height != th.Height)
                {
                    th.Height = height;
                    if (startIndexToRecalculate > index || startIndexToRecalculate < 0)
                    {
                        startIndexToRecalculate = index;
                    }
                    Invalidate(InvalidationFlag.Internal | InvalidationFlag.Appearance | InvalidationFlag.Layout);
                }
            }
        }


        /// <summary>
        /// Recalculates all top values for each item starting with a specific item.
        /// </summary>
        /// <param name="startIndexToRecalculate">Index of item from where to start.</param>
        protected virtual void RecalculateItemTopOffsets(int startIndexToRecalculate)
        {
            if (startIndexToRecalculate >= 0)
            {
                int n = itemTopHeights.Count;
                if (startIndexToRecalculate < n)
                {
                    int top = startIndexToRecalculate > 0 ? itemTopHeights[startIndexToRecalculate - 1].Bottom : 0;
                    for (int i = startIndexToRecalculate; i < n; i++)
                    {
                        ItemTopHeight th = itemTopHeights[i];
                        th.Top = top;
                        top += th.Height;
                    }
                }
                SetVirtualHeight(n > 0 ? itemTopHeights[n - 1].Bottom : 0);

                startIndexToRecalculate = -1;
                Invalidate(InvalidationFlag.Layout);
            }
        }

        protected int GetContainerOffset()
        {
            if (Squeeze)
            {
                return Math.Min(Math.Max(yOffset, 0), Math.Max(0, VirtualHeight - Height));
            }
            else
            {
                return yOffset;
            }
        }

        protected Rectangle GetVirtualizedRect()
        {
            return new Rectangle(0, GetContainerOffset(), Width, Height);
        }

        private List<int> removals = new List<int>();

        private void ReleaseItems()
        {
            if (!KeepItems)
            {
                Rectangle area = GetVirtualizedRect();
                int count = Count;
                foreach (int index in virtualItems.Keys)
                {
                    Rectangle itemRect = GetItemRect(index);
                    if (index >= count || !area.IntersectsWith(itemRect))
                    {
                        Item item = virtualItems[index];
                        OnReleaseVirtualItem(item);
                        removals.Add(index);
                    }
                }
                foreach (int index in removals)
                {
                    virtualItems.Remove(index);
                }
                removals.Clear();
            }
        }

        /// <summary>
        /// Layouts the items.
        /// </summary>
        protected virtual void LayoutItems()
        {
            //Squeeze = true;
            var visibleControls = itemContainer.VisibleControls;
            visibleControls.Clear();
            ReleaseItems();
            int yOffset = GetContainerOffset();
            int n = Count;
            if (n > 0)
            {
                int index = GetItemAtOffset(yOffset);
                if (index >= 0)
                {
                    if (animator.IsAnimated) index = OffsetIndex(index);
                    if (index < n)
                    {
                        int bottom = ClientHeight;
                        int w = ClientWidth;
                        Rectangle rect;
                        int itemTop = 0;
                        bool isAnimated = animator.IsAnimated; // note that this property might change after calling animator.ProcessOffsets().
                        do
                        {
                            object data = itemsSource[index];
                            Item item = OnGetVirtualItem(index, data, null);
                            visibleControls.Add(item);
                            virtualItems[index] = item;
                            item.Attach(index, data, this);
                            OnItemAttached(item);

                            ItemTopHeight th = itemTopHeights[index];
                            Size size = new Size(w, th.Height);
                            rect = item.Margin.Layout(item.Bounds, size);
                            rect.Y += th.Top - yOffset;
                            item.Bounds = rect;
                            if (isAnimated) animator.AdjustNextItem(item, index);
                            index++;
                            if (index >= Count) break;
                            itemTop = item.Top;
                        } while (itemTop < bottom);
                    }
                }
            }
        }

        internal protected void OnItemAttached(Item item)
        {
            if (ItemAttached != null)
            {
                var ea = CreateItemEventArgs(item.ItemIndex, item, null, null);
                ItemAttached(this, ea);
            }
        }

        private int OffsetIndex(int index)
        {
            int d = animator.GetIndexOffset(index);
            if (d > 0)
            {
                while (d > 0 && index > 0)
                {
                    d -= itemTopHeights[index].Height;
                    index--;
                }
            }
            return Math.Max(0, index);
        }

        public Rectangle GetItemRect(int itemIndex)
        {
            if (itemIndex < 0 || itemIndex >= itemTopHeights.Count) return Rectangle.Empty;
            int w = ClientWidth;
            ItemTopHeight th = itemTopHeights[itemIndex];
            return new Rectangle(0, th.Top, w, th.Height);
        }

        /// <summary>
        /// Gets the item index of the first item at the specified offset, otherwise -1.
        /// </summary>
        /// <param name="yOffset">Y-Offset for the first item.</param>
        /// <returns>Index of the first item, otherwise -1.</returns>
        public int GetItemAtOffset(int position)
        {
            if (position <= 0) return 0;
            int max = itemTopHeights.Count - 1;
            if (max < 0) return 0;
            int min = 0;
            int total = virtualHeight;
            if (position >= total) return max;

            // estimate the index by asuming that all items have the same height:
            int i = Math.Min(Count * position / total, max);

            while (min < max)
            {
                var item = itemTopHeights[i];
                int dif = item.Bottom - position;
                if (dif == 0) return i;
                if (dif < 0) min = i + 1;
                else max = i;
                i = (min + max) >> 1;
            }
            return max;
        }

        protected virtual void OnAlwaysShowGroupHeaderChanged()
        {
            Invalidate(InvalidationFlag.UpdateChildren);
        }

        /// <summary>
        /// Updates the items which are currently visible.
        /// </summary>
        public void UpdateItems()
        {
            Invalidate(InvalidationFlag.UpdateChildren | InvalidationFlag.Layout);
        }

        public virtual void OnTapItem(Item item)
        {
            if (TapItem != null)
            {
                ItemEventArgs e = CreateItemEventArgs(item.ItemIndex, item, item.DataValue, item.Host);
                TapItem(this, e);
            }
        }

        /// <summary>
        /// Determines whether an item is selected by the host.
        /// </summary>
        /// <param name="item">item to determine state.</param>
        /// <returns>True, if the item is selected, otherwise false.</returns>
        public virtual bool IsItemSelected(Item item)
        {
            if (item == null || selectedItemIndex < 0) return false;
            if (Multiselect) return item.IsSelected;
            IItemHost host = selectedItemHost;
            if (host == null) host = this;
            return (item.ItemIndex == selectedItemIndex && item.Host == host);
        }

        public virtual void SetSelectedItem(Item item)
        {
            var selected = (selectedItemHost != null) ? selectedItemHost.GetRealizedItem(selectedItemIndex) : null;

            if (item == null)
            {
                selectedItemHost = null;
                selectedItemIndex = -1;
            }
            else
            {
                selectedItemIndex = item.ItemIndex;
                selectedItemHost = item.Host;
                item.IsSelected = true;
            }
            if (selected != null) selected.IsSelected = true;
        }

        public virtual void SetSelectedItem(ListViewGroup group, int index)
        {
            if (index < 0)
            {
                selectedItemHost = null;
                selectedItemIndex = -1;
            }
            else
            {
                selectedItemIndex = index;
                selectedItemHost = group;
                //group.OnSelectionChanged(true);
            }
        }

        public virtual void OnItemRemoved(int index)
        {
            if (itemTopHeights.Count > index)
            {
                animator.ItemRemoved(index, itemTopHeights[index].Height);
            }

            itemContainer.VisibleControls.Clear();
            itemTopHeights.RemoveAt(index);
            startIndexToRecalculate = startIndexToRecalculate < 0 ? index : Math.Min(startIndexToRecalculate, index);

            int[] keys = new int[virtualItems.Count];
            int i = 0;
            foreach (int key in virtualItems.Keys)
            {
                keys[i++] = key;
            }
            foreach (int key in keys)
            {
                if (key >= index)
                {
                    var item = virtualItems[key];
                    item.ItemIndex--;
                    OnReleaseVirtualItem(virtualItems[key]);
                    virtualItems.Remove(key);
                }
            }
            Invalidate(InvalidationFlag.Internal | InvalidationFlag.Layout);
        }

        public virtual void OnItemInserted(int index)
        {
            int[] keys = new int[virtualItems.Count];
            int i = 0;
            foreach (int key in virtualItems.Keys)
            {
                keys[i++] = key;
            }
            foreach (int key in keys)
            {
                if (key >= index)
                {
                    var item = virtualItems[key];
                    item.ItemIndex = key - 1;
                    OnReleaseVirtualItem(item);
                    virtualItems.Remove(key);
                }
            }

            object data = itemsSource[index];
            int h = GetItemHeight(index, data);
            int top = index > 0 ? itemTopHeights[index - 1].Bottom : 0;
            ItemTopHeight topHeight = new ItemTopHeight(top, h);
            itemTopHeights.Insert(index, topHeight);
            startIndexToRecalculate = startIndexToRecalculate < 0 ? index : Math.Min(startIndexToRecalculate, index);
            Invalidate(InvalidationFlag.Internal | InvalidationFlag.Layout);
            if (itemTopHeights.Count > index)
            {
                animator.ItemInserted(index, itemTopHeights[index].Height);
            }

        }

        protected internal override void OnRender(Silvermoon.Core.Renderer renderer)
        {
            base.OnRender(renderer);
            if (animator.IsAnimated) Schedule(Invalidate);
        }

        private void Invalidate()
        {
            Invalidate(InvalidationFlag.Layout);
        }

        /// <summary>
        /// Brings an item into view.
        /// </summary>
        /// <param name="itemIndex">Index of the item  to bring into view.</param>
        /// <param name="animated">True to perform animation, otherwise false.</param>
        /// <exception cref="ArgumentOutOfRangeException">if itemIndex is outside bounds.</exception>
        public virtual void BringItemIntoView(int itemIndex, bool animated)
        {
            if (itemIndex < 0 || itemIndex >= Count) throw new ArgumentOutOfRangeException("itemIndex");
            Rectangle bounds = GetItemRect(itemIndex);
            itemContainer.BringIntoView(bounds, animated);
        }

        /// <summary>
        /// Brings the specified rectangle within an item into view.
        /// </summary>
        /// <param name="itemIndex">Index of the item  to bring into view.</param>
        /// <param name="rect">The rectangle of the client bounds in the item to bring into view.</param>
        /// <param name="animated">True to perform animation, otherwise false.</param>
        /// <exception cref="ArgumentOutOfRangeException">if itemIndex is outside bounds.</exception>
        public virtual void BringItemRectIntoView(int itemIndex, Rectangle rect, bool animated)
        {
            if (itemIndex < 0 || itemIndex >= Count) throw new ArgumentOutOfRangeException("itemIndex");
            Rectangle itemRect = GetItemRect(itemIndex);
            rect.Offset(itemRect.X, itemRect.Y);
            itemContainer.BringIntoView(rect, animated);
        }

        protected override void OnSizeChanged()
        {
            base.OnSizeChanged();

            foreach (var item in virtualItems.Values)
            {
                // since ListViewGroup uses the  GetVirtualizedRect() which depends on the size of the listview at LayoutItems(),
                // it would not recognize that there are more or less items are viewable. Therefore each item must be notified:
                item.Invalidate(InvalidationFlag.Layout);
            }
        }

        #region IItemHost Members

        bool IItemHost.IsDynamicItemHeightSupported
        {
            get { return true; }
        }

        #endregion


        #endregion
        #region events

        public event EventHandler<ItemEventArgs> ItemAttached;

        /// <summary>
        /// Occurs when a ListViewItem needs to be accessed.
        /// </summary>
        public event EventHandler<ItemEventArgs> GetVirtualItem;

        /// <summary>
        /// Occurs when a virtual item container is released from being used.
        /// </summary>
        public event EventHandler<ItemEventArgs> ReleaseVirtualItem;

        public event EventHandler<ItemEventArgs> TapItem;

        /// <summary>
        /// Occurs when VirtualHeight changes.
        /// </summary>
        public event EventHandler VirtualHeightChanged;

        #endregion

    }
}
